/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package technotheist.geo.map;

import com.jme3.math.FastMath;
import java.nio.Buffer;
import static technotheist.geo.map.MapUtil.*;

/**
 *
 * @author Mitchel Strachan
 */
public abstract class Map<T extends Buffer> {

    /*
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     * 
     */
//    protected final byte detail;
    //
    public final int sideLength;
    public final int elementCount, componentCount, byteCount;
    public final Format format;
    public final int components;
    public final float uvNormalize;
    private final int[] pentIndices;
    private final int[][] pentNeighbours;

    public boolean isPent(int index) {
        return pentIndices[0] == index 
                || pentIndices[1] == index 
                || pentIndices[2] == index 
                || pentIndices[3] == index 
                || pentIndices[4] == index 
                || pentIndices[5] == index 
                || pentIndices[6] == index 
                || pentIndices[7] == index 
                || pentIndices[8] == index 
                || pentIndices[9] == index 
                || pentIndices[10] == index 
                || pentIndices[11] == index;
    }
    
    protected Map(int size, Format format) {
        this(size, 1, format);
    }

    protected Map(int size, int components, Format format) {
//        this.detail = detail;
        this.sideLength = size;
        this.format = format;
        this.components = components;
        this.elementCount = PANELS * size * size + 2;
        this.componentCount = elementCount * components;
        this.byteCount = componentCount * format.size;
        this.uvNormalize = 1.0f / sideLength;
        this.pentIndices = PentIndices(sideLength);
        this.pentNeighbours = PentNeighbours(sideLength);
    }

    
    private final MapPos tmp = new MapPos();
    public void getNeighbours(int index, int[] store) {
        int p = pentID(index);
        if (p < 0) {
            indexToPos(index, tmp);
            getHexNeighbours(tmp, store);
        } else {
            getPentNeighbours(p, store);
        }
    }

    private void getHexNeighbours(MapPos pos, int[] store) {
        getHexNeighbours(pos.panel, pos.u, pos.v, store);
    }
    private void getHexNeighbours(int pnl, int u, int v, int[] store) {
        store[NW] = index(pnl, u - 1, v);
        store[WW] = index(pnl, u - 1, v - 1);
        store[SW] = index(pnl, u, v - 1);
        store[SE] = index(pnl, u + 1, v);
        store[EE] = index(pnl, u + 1, v + 1);
        store[NE] = index(pnl, u, v + 1);
    }

    private void getPentNeighbours(int pentID, int[] store) {

        store[0] = pentNeighbours[pentID][0];
        store[1] = pentNeighbours[pentID][1];
        store[2] = pentNeighbours[pentID][2];
        store[3] = pentNeighbours[pentID][3];
        store[4] = pentNeighbours[pentID][4];
        store[5] = -1;
    }

    public void getNeighbours(int pnl, int u, int v, int[] store) {
        int p = pentID(index(pnl, u, v));
        if (p < 0) {
            getHexNeighbours(pnl, u, v, store);
        } else {
            getPentNeighbours(p, store);
        }
    }

    public MapPos indexToPos(int index) {
        return indexToPos(index, new MapPos());
    }

    public MapPos indexToPos(int index, MapPos pos) {
        MapPos mp = pos == null ? new MapPos() : pos;
        IndexToPos(index, sideLength, pos);
        return pos;
    }
    public MapUV indexToUV(int index) {
        return indexToUV(index, new MapUV());
    }

    public MapUV indexToUV(int index, MapUV pos) {
        MapUV mp = pos == null ? new MapUV() : pos;
        IndexToUV(index, sideLength, pos);
        return pos;
    }

    private int pentID(int i) {
        for (int p = 0; p < PENT_COUNT; p++) {
            if (pentIndices[p] == i) {
                return p;
            }
        }
        return -1;
    }

    public final int getInt(int p, int u, int v, int component) {
        if (component < 0 || component >= components) {
            throw new IllegalArgumentException("component must be between 0 and " + components);
        }
        return getInt(index(p, u, v) + component);
    }

    public final float getFloat(int p, int u, int v, int component) {
        if (component < 0 || component >= components) {
            throw new IllegalArgumentException("component must be between 0 and " + components);
        }
        return getFloat(index(p, u, v) + component);
    }

    
    public final void setInt(int p, int u, int v, int component, int value) {
        if (component < 0 || component >= components) {
            throw new IllegalArgumentException("component must be between 0 and " + components);
        }
        setInt(index(p, u, v) + component, value);
    }

    public final void setFloat(int p, int u, int v, int component, float value) {
        if (component < 0 || component >= components) {
            throw new IllegalArgumentException("component must be between 0 and " + components);
        }
        setFloat(index(p, u, v) + component, value);
    }

    public final int getInt(int p, int u, int v) {
        return getInt(p, u, v, 0);
    }

    public final float getFloat(int p, int u, int v) {
        return getFloat(p, u, v, 0);
    }

    public final void setInt(int p, int u, int v, int value) {
        setInt(p, u, v, 0, value);
    }

    public final void setFloat(int p, int u, int v, float value) {
        setFloat(p, u, v, 0, value);
    }

    protected abstract T data();

    public abstract int getInt(int bufferIndex);

    public abstract float getFloat(int bufferIndex);

    public abstract void setInt(int bufferIndex, int value);

    public abstract void setFloat(int bufferIndex, float value);

    public float get(MapUV mp) {
        return getNor(mp.panel, mp.u, mp.v);
    }

    public float get(MapUV mp, int component) {
        return getNor(mp.panel, mp.u, mp.v, component);
    }

    public float getNor(int p, float nu, float nv) {
        return getNor(p, nu, nv, 0);
    }

    public float get(int p, float u, float v) {
        return get(p, u, v, 0);
    }

    public float getNor(int p, float nu, float nv, int component) {
        return get(p, nu * sideLength, nv * sideLength + 1, component);
    }

    public float get(int p, float u, float v, int component) {
        int u0 = (int) FastMath.floor(u);
        int v0 = (int) FastMath.floor(v);

        float du = u - u0;
        float dv = v - v0;

        return getFloat(index(p, u0, v0) + component) * (1 - du) * (1 - dv)
                + getFloat(index(p, u0 + 1, v0) + component) * du * (1 - dv)
                + getFloat(index(p, u0, v0 + 1) + component) * (1 - du) * dv
                + getFloat(index(p, u0 + 1, v0 + 1) + component) * du * dv;
    }

    public int index(int p, int u, int v) {
        return Index(p, u, v, sideLength) * components;
    }

    /*
     * 
     * 
     * 
     * 
     */
    protected static int VertexCount(byte detail) {
        return 10 * (1 << (2 * detail)) + 2;
    }
//    protected static int Index(byte detail, int p, int u, int v) {
//        int sideLength = 1 << detail;
//        int elementCount = 10 * sideLength * sideLength + 2;
//        return Index(p, u, v, sideLength);
//    }
}
